React项目实战

(一) 创建项目

  1. npm i create-react-app -g 安装脚手架(需要node14以上)

  2. create-react-app my-app 创建项目

  3. 用vscode打开项目my-app

  4. npm start 运行项目

    win7安装node14:

  5. 若安装有nvm, 先卸载

  6. 下载node14解压到c盘

  7. 右键我的电脑 => 属性 => 高级系统设置 => 环境变量 => 系统变量 => 双击Path

  8. 查看有Path里node的路径, 将路径改为

    C:\node-v14.15.3-win-x64;NODE_SKIP_PLATFORM_CHECK=1
    

(二) 路由配置

(1) 配置路由步骤

  1. npm i react-router-dom 安装路由插件
  2. 创建组件: 首页,目的地,提交需求,发现,我的
  3. 在app.js配置路由
    1. Router 最外层需要用Router进行包裹(只需一次)
    2. Routes 路由组件包裹层
    3. Route 用来配置路由,包括路由地址和路由组件
    4. 重定向使用Navigate
// App.js
import { BrowserRouter as Router, Routes, Route,Navigate} from "react-router-dom";
import Home from "./pages/home/index"; 
import Demand from "./pages/demand/index";   

function App() {
  return (
    <div className="app">
      <Router>
        <Routes> 
          {/*重定向*/}
          <Route path="/" element={<Navigate to="/house"/>} />
          <Route path="/house" element={<Home />}>房子</Route>
          <Route path="/demand" element={<Demand />}> 需求 </Route> 
        </Routes> 
      </Router>
    </div>
  );
}

export default App;

(2) 子路由配置

// App.js配置
import { BrowserRouter as Router, Routes, Route} from "react-router-dom";
import Home from "./pages/home/index";
// Home子路由组件
import Detail from "./pages/home/detail";
import List from "./pages/home/list";

import Demand from "./pages/demand/index";  

function App() {
  return (
    <div className="app">
      <Router>
        <Routes>
         
          <Route path="/house" element={<Home />}>
            {/* 嵌套路由(子路由)配置 */}
            <Route path="detail" element={<Detail />} />
              {/*访问/home,默认显示list*/}
            <Route index element={<List />} />
          </Route>

          <Route path="/demand" element={<Demand />}> 需求 </Route> 
        </Routes> 
      </Router>
    </div>
  );
}

export default App;
// home.jsx 父路由
import { Outlet } from "react-router-dom";

function Index() {
  return (
    <div>
      <h3>父组件</h3>
      <hr />
      <Outlet />
    </div>
  );
}

export default Index;

(3) 重定向

  1. react-router-dom v6以前的版本, 有一个Redirect组件可以实现

  2. v6的重定向, 官方给出的参考是

    import { useEffect } from "react";
    import { useNavigate } from "react-router-dom";
    
    function Redirect({ to }) {
      let navigate = useNavigate();
      useEffect(() => {
        navigate(to);
      });
      return null;
    }
    
    // usage
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/events" element={<Users />} />
      <Route
        path="/dashboard"
        element={<Redirect to="/events" />}
      />
    </Routes>; 
    

(4) 编程式导航

(三) 添加预处理语言支持

  1. 添加scss支持(不要安装node-sass, 有很多问题)
npm install sass
  1. 添加less支持
    1. npm i less less-loader@6 less-loader版本太高则不兼容
    2. npm run eject 暴露webpack配置
    3. 在webpack.config.js修改散出地方, 把sass修改为了less
// 1. 在64行中,将
const sassRegex = /\.(scss|sass)$/;
const sassModuleRegex = /\.module\.(scss|sass)$/;
修改成下面的代码(若想保留sass则复制后再修改)
const lessRegex = /\.(less)$/;
const lessModuleRegex = /\.module\.(less)$/;

// 2. 在502行, 将sass修改为less, 如果想保留sass就复制一份出来进行修改
{
    test: sassRegex,
        exclude: sassModuleRegex,
            use: getStyleLoaders(
                {
                    importLoaders: 3,
                    sourceMap: isEnvProduction
                    ? shouldUseSourceMap
                    : isEnvDevelopment,
                },
                'sass-loader'
            ),
                // Don't consider CSS imports dead code even if the
                // containing package claims to have no side effects.
                // Remove this when webpack adds a warning or an error for this.
                // See https://github.com/webpack/webpack/issues/6571
                sideEffects: true,
},
    // Adds support for CSS Modules, but using SASS
    // using the extension .module.scss or .module.sass
    {
        test: sassModuleRegex,
            use: getStyleLoaders(
                {
                    importLoaders: 3,
                    sourceMap: isEnvProduction
                    ? shouldUseSourceMap
                    : isEnvDevelopment,
                    modules: {
                        getLocalIdent: getCSSModuleLocalIdent,
                    },
                },
                'sass-loader'
            ),
    }, 

(四) 导入ant-design

官网地址

(五) 父子组件通信

// 父组件
import React from 'react';
import Son from './Son'

class Father extends React.Component{
    constructor() {
        super();
        this.getMsg = this.getMsg.bind(this);
    }

    // 接收子组件传过来的数据
    getMsg(data) {
        console.log(data);
    }

    render() {
        return(
            <div>
                <h3>父组件</h3>
                <hr/>
                {/* 给子组件传递属性和方法 */}
                <Son msg="hello son" getMsg={this.getMsg}/>
            </div>
        )
    }
}

export default Father;

// 子组件
import React from 'react';

class Son extends React.Component{
    constructor() {
        super();
    }

    render() {
        let {msg,getMsg} = this.props;
        return(
            <div>
                {msg}<br/>
                {/* 调用父组件传过来的方法,并传递数据 */}
                <button onClick={()=>{getMsg('哈哈哈哈哈')}}>发送消息</button>
            </div>
        )
    }
}

export default Son;			

(六) axios 拦截器

import axios from "axios"; 
let isDev = process.env.NODE_ENV === 'development';
let baseURL;
if (isDev) {
    baseURL = "http://localhost:3006";
} else {
    baseURL = "http://huruqing.cn:3006";
}
const service = axios.create({
  baseURL,
  timeout: 10 * 60 * 1000,
});

//4. 请求拦截
service.interceptors.request.use(
  (config) => {
    // do something
    return config;
  },
  (err) => {
    return Promise.reject(err);
  }
);

service.interceptors.response.use(
  (res) => {
    const data = res.data;
    if (data.code != 666) {
      return Promise.reject(data.msg);
    }
    return data;
  },
  (err) => {
    return Promise.reject(err);
  }
);

const get = (url, data = {}) => {
  return service.get(url, { params: data });
};
const post = (url, data = {}) => {
  return service.post(url, data);
};

export default {
  get,
  post,
};

(七) 静态页面

  1. 引入icont
  2. 使用短路径

    在React项目使用短路径, 跟 ../../../../地狱模式说拜把

    项目的根目录下创建jsconfig.json文件,并添加以下代码。

{
  "compilerOptions": {
    "baseUrl": "src"
  },
  "include": ["src"]
}

// 使用
import axios from "../../../utils/request";   // 以前的写法
import axios from "utils/request";   
  1. 图片的引用问题

最佳实践: 写静态的时候, 如果图片来自网上, 随便用网上的一张图片暂代, 等        拿到数据再使用变量代替

方式1: 支持完整路径
<img sr="https://mall.s.maizuo.com/aaa.png"/>
方式2: 使用import
import bg from 'assest/img/bg.png'
 <img src={bg} alt=""/>
  1. 渲染还可以这样写
{
	list.map(item=>(
  <ul>
  	<li>11111</li>
    <li>22222</li>
  </ul>
  ))
}

// 类似vue的template的做法
{
	list.map(item=>( 
  <>
  	<div>11111</div>
    <div>22222</div> 
   </> 
  ))
}


  1. react渲染html字符串(类似vue的v-html指令)
let htmlStr = `<ul class="nav fcc bg-fff">
      <li class="fg1 ifcc item active">推荐•路线</li>
      <li class="fg1 ifcc item">推荐•路线</li>
      <li class="fg1 ifcc item">推荐•路线</li>
    </ul>`

export default function() {
	return (
   <div className="pt-15 bg-fff" dangerouslySetInnerHTML={{ __html:htmlStr}}></div> 
)
}

(八) classnames

类似vue绑定class的操作

// 判断条件为true时会给div添加class类 on
<div className={classnames("flex jc-sa",{on:判断条件}}></div>

// 实际应用
import React from 'react'; 
import classnames from 'classnames';
export default function() {
    let curr = '01';
    return(
        <div className="demo-index">
           <li className={classnames("flex f18 jc-sb",{on: curr==='01'})}>111</li>
           <li  className={classnames("flex f18 jc-sb",{on: curr==='02'})}>222</li>
           <li  className={classnames("flex f18 jc-sb",{on: curr==='03'})}>333</li>
        </div>
    )
}

(九) Hooks

react hooks是什么

  1. 在函数组件中没有state(状态), 也没有生命周期
  2. react hooks是react中引入新特性,它可以让react函数组件也拥有状态

(1) useState

  1. 在函数组件中使用useState来定义响应式数据, 接收一个参数(变量的默认值)
  2. 使用格式 let [value,setValue] = useState(initValue)
    • value要定义的变量名称
    • setValue用来修改value的方法
    • initValue初始值

value是基本数据类型

import React, { useState } from 'react';
export default function() {
    // 定义响应式数据keyword,keyword的默认值是 ''
    let [keyword,setKeyword] = useState(''); 
    return(
        <div>
            <button onClick={()=>{setKeyword('哈哈哈哈哈')}}>修改关键字</button>
            <p>{keyword}</p> 
        </div>
    )
}

value是对象

import React, { useState } from "react";

function Demo() {
  let [obj, setObj] = useState({
    username: "张三",
    age: 18,
  });

  const handleClick = (key, value) => {
    // 需要传入一个新的对象
    setObj({
      ...obj,
      [key]: value,
    });
  };

  return (
    <div>
      <p>
        {obj.username}
		<button onClick={() => { handleClick("username", "李四"); }} > 修改姓名 </button>
      </p>
      <p>
        {obj.age} 
		<button onClick={() => { handleClick("age", 20); }} > 修改年龄 </button>
      </p>
    </div>
  );
}

export default Demo;

value是数组

import React, { useState } from "react";

function Demo() {
  let [list, setList] = useState([
    { username: "张三", finished: 17 },
    { username: "李四", finished: 18 },
    { username: "王五", finished: 19 },
  ]);

  const update = (item)=> {
    item.finished = !item.finished;
    // 需要传入一个新的数组
    setList([...list]);
  }
 
  return (
    <ul>
      {list.map((item, index) => (
        <li key={index}>
          <span>{item.username} </span>
          {item.finished?'已完成':'未完成'}
          <button onClick={()=>{update(item)}}>修改状态</button>
        </li>
      ))}
    </ul>
  );
}

export default Demo;

(2) useEffect

  1. 中括号内的变量发生了改变, 就会触发回调函数
  2. 请求后台接口可以放在useEffect的函数里执行, 中括号为空或者不要中括号
    let [keyword,setKeyword] = useState(''); 
    useEffect(()=> {
        console.log(2222);
      // 当keyword发生变化时,会触发回调函数
    },[keyword])

(3) useNavigate

import {useNavigate} from 'react-router';
 // 跳转到/home/detail
 navigate('/home/detail'});
//  // 跳转到/home/detail并传参参数
 navigate('/home/detail',{ 
      state: {
        id: item.id
      }
})

(十) 路由跳转和传参

路由跳转:

1. 使用 <Link to="xxx">xxx</Link>
2. 通过 navigate进行跳转
 - 一个参数: 目的地路由
 - 两个参数: 目的路由, state数据

(1) 通过state传参

传递数据

// 列表页面
import {useNavigate} from 'react-router';

function House({ houseList}) {
  const navigate = useNavigate();
  // 跳转到详情
  const toDetail = (item) => { 
    navigate('/home/detail',{
      // state里放要传递的参数
      state: {
        id: item.id
      }
    })
  }

接收参数

// 详情页面
import {useLocation} from 'react-router';
function List() {
    const location = useLocation();
    console.log(location);
    return ( <div>详情</div> );
}
export default List;

(2) 动态路由传参

配置动态路由(app.js)

 <Route path="/demo2/:id" element={<Demo2 />} />

路由跳转并传参(demo.jsx);

import React from "react";
import { Link } from "react-router-dom";
import {useNavigate} from 'react-router'

function Demo() {
  const navigate = useNavigate();
  return (
    <div>
      <Link to="/demo2/2222">去demo2</Link>
      <button onClick={()=>{navigate('/demo2/3333')}}>去demo2</button>
    </div>
  );
}

export default Demo;

接收参数

import React from "react";
import {useParams} from 'react-router';

function Demo2() {
  const params = useParams();
  console.log(params); 
  return (
    <div>
      <p>{params.id}</p>
    </div>
  );
}

export default Demo2;

(十一) redux

本例gitee地址: git@gitee.com:huruqing/react-redux-demo.git

新版redux,使用hooks https://react-redux.js.org/tutorials/quick-start

(1) redux使用场景

mobx也是用来进行跨组件通信, 比redux更简单一些 一句话: 状态共享, 跨组件通信

某个组件的状态,需要共享
某个状态需要在任何地方都可以拿到
一个组件需要改变全局状态
一个组件需要改变另一个组件的状态

(2) 核心概念

  1. **Store: **Store 就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个 Store。

Redux 提供createStore这个函数,用来生成 Store。

  1. **State: **状态, 存放在store里
  2. Action
    1. action是个对象, 描述当前发生的事情
    2. Action Creator: View 要发送多少种消息,就会有多少种 Action。如果都手写,会很麻烦。可以定义一个函数来生成 Action,这个函数就叫 Action Creator。
    3. 派发action: store.dispatch() 是 View 发出 Action 的唯一方法。
  3. Reducer
    1. Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。
    2. Reducer 是一个纯函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。上面的createStore接受 Reducer 作为参数,生成一个新的 Store
    3. 纯函数: 纯函数是函数式编程的概念,必须遵守以下一些约束。
      1. 特点: 相同的输入会有相同的输出
      2. 不得改写参数
      3. 不能调用系统 I/O 的API(就是输入输出)
      4. 不能调用Date.now()或者Math.random()等不纯的方法,因为每次会得到不一样的结果

(3) 项目中redux步骤

知识目录
  1. 创建store
  2. 获取状态
  3. 修改状态
  4. 模块化
  5. 持久化
1. 安装redux和react-redux

npm i redux react-redux

2. 创建store和相关的东西
  1. src下新建 /redux/index.js
  2. 初始化state、声明action、声明reduer
  3. 创建store并导出
import {createStore} from 'redux';

// 1.state的初始值
const initState = {
    count: 0
}

// 2.声明action creator 并导出,payload是派发action时传进来的数据
export const updateCount = function(payload) {
    // 此函数返回一个action
    return {
        type: 'UPDATE_COUNT',
        payload,
    }
}

// 3.声明reducer
const reducer = function(state=initState,action) {
    let {type,payload} = action;
    switch(type) {
        case 'UPDATE_COUNT':
            return {
                ...state,
                count: state.count+payload
            }
        default:
            return state;    
    }
}

let store = createStore(reducer);
export default store;
3. 挂载store
// index.js(src目录下的)
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {Provider} from 'react-redux';
import store from 'redux/index'

ReactDOM.render(
  <React.StrictMode>
  	// 挂载store
    <Provider store={store}>
    <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
); 
reportWebVitals();
4. 获取和修改store的状态

把普通react组件改造成react高阶组件

import React from "react"; 
// 1 导入action creator等相关东西
import { updateCount } from "redux/index";  
import { bindActionCreators } from "redux"; 
import { connect } from "react-redux";

function Demo(props) {
  return ( 
      <div>
          {/* 5.获取count的值 */}
          <p>count的值: {props.count}</p>

           {/* 修改count的值 */}
           <button onClick={()=>{props.updateCount(props.count+1)}}>修改count</button>
      </div>
  );
}

// 2.把store里state转变成props来使用,后面访问state时就使用props.xxx来进行访问
// @params state store里面所有状态
function mapStateToProps(state) {
  return {
    // 组件使用props.count来访问count状态
    count: state.count 
  }
}
// 3.把dispatch转变成props来使用
function mapDispatchToProps(dispatch) {
  return {
    // bindActionCreators的作用:把action creator和dispatch绑定起来
    updateCount: bindActionCreators(updateCount,dispatch)
  }
}

// 4.connect方法的作用: 把普通组件Demo变高阶组件
export default connect(mapStateToProps,mapDispatchToProps)(Demo);

(4) redux 模块化

  1. 新增cart模块, 在里面定义state,action,reducer并导出
// demo模块

// 1.state的初始值
const initState = {
    count: 10
}

// 2.声明action creator 并导出,payload是派发action时传进来的数据
export const updateCount = function(payload) {
    // 此函数返回一个action
    return {
        type: 'UPDATE_COUNT',
        payload,
    }
}

// 3.声明reducer
export default function(state=initState,action) {
    let {type,payload} = action; 
    switch(type) {
        case 'UPDATE_COUNT':
            return {
                ...state,
                count: payload
            }
        default:
            return state;    
    }
}

// user模块
// 1.usermok的初始化状态
const initState = {
  phone: "",
  token: "",
};

// 2.相关action creator
export const updatePhone = function (payload) {
  // 返回一个action
  return {
    type: "UPDATE_PHONE",
    payload,
  };
};
export const updateToken = function (payload) {
  return {
    type: "UPDATE_TOKEN",
    payload
  };
};

// 3.声明reducer
export default function (state = initState, action) {
  let { type, payload } = action; 
  switch (type) {
    case "UPDATE_PHONE":
      return {
        ...state,
        phone: payload,
      };
    case "UPDATE_TOKEN":
      return {
        ...state,
        token: payload,
      };

    default:
      return state;
  }
}

// 合并reducer, /src/redux/index.js
import {createStore} from 'redux';
import demoReducer from './demo';
import userReducer from './user';
import { combineReducers } from 'redux';

// 合并reducer
let reducers = combineReducers({
    demo: demoReducer,
    user: userReducer
})

export default createStore(reducers);

  1. 合并reducer, 改造/redux/index.js 代码
import {createStore} from 'redux';
import demoReducer from './demo';
import userReducer from './user';
import { combineReducers } from 'redux';

// 合并reducer
let reducers = combineReducers({
    demo: demoReducer,
    user: userReducer
})

export default createStore(reducers);

(5) redux 持久化

1. 安装持久化插件 redux-persist
npm i redux-persist
2. 使用插件对store进行持久化操作

改造 /redux/index.js 代码, 对reducer和store进行持久化配置

3. 应用持久化后的store

改造 /src/index.js, 实现持久化

*************************模块化之前持久化**************************
 // src/redux/index.js 
import { createStore } from "redux";
import {persistStore, persistReducer} from 'redux-persist';
import storageSession from 'redux-persist/lib/storage/session';

// 初始状态
const initState = {
  count: 0, 
}; 
 
export const updateCount = (payload) => {
  return {
    type: "UPDATE-COUNT",
    payload,
  };
};

// 修改状态的一个方法, 叫reducer, 当用户派发了一个action, 不管是什么action, 这里都会接收到 
const reducer = (state = initState, action) => {
  let { type, payload } = action;
  switch (type) {
    case "UPDATE-COUNT":
      return {
        ...state,
        count: state.count + payload,
      };
      break; 
    default: {
      return state;
    }
  }
};

const storageConfig = {
    key: 'root', // 必须有的
    storage:storageSession, // 缓存机制
    blacklist: [] // reducer 里不持久化的数据,除此外均为持久化数据
}
const newReducer = persistReducer(storageConfig, reducer);
export let store = createStore(newReducer);
export let newStore = persistStore(store);

/**
 * 模块之前的持久化
 * src/index.js
 */ 
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import "antd-mobile/dist/antd-mobile.css";
import { Provider } from "react-redux";
import {store,newStore} from "./redux/index";
import { PersistGate } from "redux-persist/lib/integration/react";

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <PersistGate persistor={newStore}>
        <App />
      </PersistGate>
    </Provider>
  </React.StrictMode>,
  document.getElementById("root")
); 
reportWebVitals();
*************************模块之后的持久化**************************
// src/redux/index.js代码
import {createStore} from 'redux';
import demoReducer from './demo';
import userReducer from './user';
import { combineReducers } from 'redux';


// 1.导入相关插件
// persistStore持久化store, 持久化reducer
import {persistStore, persistReducer} from 'redux-persist';
// storageSession缓存
import storageSession from 'redux-persist/lib/storage/session';

// 2.store持久化设置
const storageConfig = {
    key: 'root', // 必须有的
    storage:storageSession, // 缓存机制
    blacklist: [] // reducer 里不持久化的数据,除此外均为持久化数据
}

// 3.合并reducer
let reducers = combineReducers({
    demo: demoReducer,
    user: userReducer
})

// 4.给reducer应用持久化配置
const newReducers = persistReducer(storageConfig, reducers);
// 5.创建store
const store = createStore(newReducers);
// 6.对store进行持久化并导出
export const persistor = persistStore(store);
// 7.导出store
export default store;


// src/index.js代码
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { Provider } from "react-redux"; 

// 8.持久化
import store, { persistor } from "./redux/index";
import { PersistGate } from "redux-persist/lib/integration/react";

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
        {/* 用持久化后的store包裹<App/> */}
      <PersistGate persistor={persistor}>
        <App />
      </PersistGate>
    </Provider>
  </React.StrictMode>,
  document.getElementById("root")
);
reportWebVitals();

(6) 异步action,使用redux-thunk实现

  1. 安装redux-thunk npm i redux-thunk
  2. 改造/redux/index.js
// 核心代码如下
//1.applyMiddleware 加载中间件
import { createStore,applyMiddleware } from "redux";
//2.导入异步中间件thunk
import thunk from 'redux-thunk';
 
const store = createStore(newReducers,applyMiddleware(thunk)); 
  1. 创建异步action

改造/redux/demo.js

// 1.state的初始值
const initState = {
  count: 10,
};

// 2.声明action creator 并导出,payload是派发action时传进来的数据
// export const updateCount = function(payload) {
//     // 此函数返回一个action
//     return {
//         type: 'UPDATE_COUNT',
//         payload,
//     }
// }

export const updateCount = (payload) => {
  // 异步action, 允许返回一个函数,使用dispatch派发action
  return (dispatch) => {
    setTimeout(() => {
      dispatch({
        type: "UPDATE_COUNT",
        payload,
      });
    }, 1000);
  };
};

// 3.声明reducer
export default function (state = initState, action) {
  let { type, payload } = action;
  switch (type) {
    case "UPDATE_COUNT":
      return {
        ...state,
        count: statepayload,
      };
    default:
      return state;
  }
}

(7) axios拦截器给http请求头添加token

配置后再查看首页的请求是否都带上了token

import axios from 'axios'; 
// 1.导入redux
import redux from 'redux/index';

 
const service = axios.create({ 
    baseURL: 'http://132.232.87.95:3006',  
    timeout: 10 * 60 * 1000 //10分钟  
}) 
service.interceptors.request.use(
    (config) => {
        // 2.获取store
        let store = redux.getState();
        // 3.把token放入请求头
        config.headers['user-token'] = store.user.token;
        return config;
    },
    (err) => {
        return Promise.reject(err);
    }
) 

service.interceptors.response.use(
    (res) => {
        const data = res.data; 
        if (data.code != 666) {  
            return Promise.reject(data.msg);
        }
        return data;
    },
    (err) => {
        return Promise.reject(err);
    }
)


const get = (url,data={})=> {
    return service.get(url,{params: data});
}
const post = (url,data={})=> {
    return service.post(url,data);
}
 
export default {
    get,post
};

(十二) Mobx

介绍

  1. mobx是react用来跨组件通信的一个插件, 它的核心是依赖注入
  2. 具体做法就是:
    • 先创建一个(或多个)可观察的仓库
    • 在需要使用仓库状态的组件, 使用enject方法将这个仓库注入到它的props里, 就可以使用仓库的状态了
    • 若组件要修改仓库的状态, 则需要将组件用observer方法成包装高级组件

(1) 安装依赖

npm install mobx mobx-react

(2) 配置store

// src/store/index.js

  1. makeAutoObservable 使一个普通类的属性和方法变成一个响应式的状态库

  2. 函数前面加get, 类似vuex的getters, 用来根据已有属性计算出一个新的属性

   import * as mobx from "mobx";
   class Store {
     count = 0;
   
     constructor() {
       mobx.makeAutoObservable(this);
     }
   
     add() {
       this.count++;
     }
     minus() {
         this.count--;
     } 
   
     // 求平方根
     get sqrt() {
       return Math.sqrt(this.count);
     }
   }
   
   export default new Store();

(3) 挂载

​ // src/index.js

​ Provider: 提供数据

import React from "react";
import ReactDOM from "react-dom";
import "./index.scss";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { Provider } from "mobx-react";
import store from "store";

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById("root")
);

reportWebVitals();

(4) 使用和修改

  1. inject 将store注入到props里
  2. observer 将组件变成高级组件, 可以修改仓库状态
  3. 在不需要修改状态的组件里, 只需要注入即可
import { inject, observer } from "mobx-react";
import React from "react";

function Demo({ store }) {
  return (
    <div style={{margin:'30px'}}>
      <button onClick={()=>{store.add()}}>+</button>
      <button>{store.count}</button>
      <button onClick={()=>{store.minus()}}>-</button>
      <p>
        开平方根: {store.sqrt.toFixed(2)}
      </p>
    </div>
  );
} 
export default inject("store")(observer(Demo));

错误: Support for the experimental syntax 'decorators-legacy' isn't currently enab -- 装饰器@

[报错显示:Parsing error: This experimental syntax requires enabling one of the following parser plugin(s): “decorators-legacy”, “decorators”.](https://blog.csdn.net/lfy_wybss/article/details/122079178)

(十三) 性能优化

知识点

  1. shouldComponentUpdate
  2. PureComponent
  3. useMemo

(1) shouldComponentUpdate

类(class)组件性能9优化可以使用shouldCompoentUpdate进行优化

// nextProps下一个属性(新的属性),nextState下一个状态(新的状态)   
shouldComponentUpdate(nextProps, nextState) { 
    // 根据新的属性或者状态决定要不要更新页面
   if(xxx) {
       return true;
	} else {
       return false
   }
}

(2) PureComponent

类(class)组件, 可以让其继承React.PureComponent来实现优化

import React from "react"; 
class Demo extends React.PureComponent {
  constructor(props){
    super(props);
  } 

  render() {
    console.log(this.props);
    return (<div>性能优化</div>)
  }
} 
export default Demo;

(3) memo

函数组件可以使用React.memo进行包装以实现优化

import React from "react";
function Scene(props) { 
  return <div>{props.name}</div>;
}
export default React.memo(Scene);

https://www.jianshu.com/p/b3d07860b778